package com.springframework.context;

import com.springframework.annotation.*;

import java.io.File;
import java.io.FileFilter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

// spring基于注解配置的上下文类
public class YcAnnotationConfigApplicationContext implements YcApplicationContext {
    // 保存所有的bean实例对象
    private Map<String, Object> beanMap = new ConcurrentHashMap<>();

    // 保存所有的bean定义信息
    public Map<String, YcBeanDefinition> beanDefinitionMap = new HashMap<>();


    // 构造方法
    public YcAnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
        // 1、解析配置类  2、创建beanMap  3、ioc  4、di
        for (Class<?> cls : annotatedClasses) {
            // 解析配置类中的@YcConfiguration注解  @YcBean注解
            parseBeanAnnotation(cls);
            // 解析配置类中  @YcComponentScan注解   这代表配置类要扫描的包
            parseComponentScan(cls);

            try {
                /**
                 * 创建 bean实例对象，并放入beanMap中
                 */
                creatBean();

                /**
                 * 完成依赖注入
                 */
                doDI();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    // 注入   就是一些对象里面的一些属性，通过set方法注入
    private void doDI() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
            String beanId = entry.getKey();
            Object beanObj = entry.getValue();   // 获取bean对象
            // 查看属性上是否有DI注解  主要是 YcResource 和 YcAutowired
            Field[] fields = beanObj.getClass().getDeclaredFields();   // 获取属性
            for (Field field : fields) {
                if (field.isAnnotationPresent(YcResource.class)) {
                    // 根据名字注入
                    YcResource ycResource = field.getAnnotation(YcResource.class);
                    // 获取注解的value值  就相当于 spring中 @Resource(name = "myBean")  的 name
                    String toDIBeanId = ycResource.value();

                    Object obj = getToDiObject(toDIBeanId);

                    // 设置此 filed 为可访问共有的
                    field.setAccessible(true);
                    field.set(beanObj, obj);

                } else if (field.isAnnotationPresent(YcAutowired.class)) {
                    // todo: 取出属性的类型，按照类型先从 beanMap中返回，如果没有再从beanDefinitionMap中操作
                    Class<?> fieldType = field.getType();
                }
            }
        }

    }


    // 根据名字取对象
    private Object getToDiObject(String toDIBeanId) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        // 如果实例map中有，直接返回
        if (beanMap.containsKey(toDIBeanId)) {
            return beanMap.get(toDIBeanId);
        }
        // 如果定义map中没有,说明扫描的时候这个 名字根本没有
        if (beanDefinitionMap.containsKey(toDIBeanId) == false) {
            throw new RuntimeException("Expected at least one candidate bean but found none");
        }
        // 走到这里证明 定义map中存在，但是实例map中不存在
        YcBeanDefinition bd = this.beanDefinitionMap.get(toDIBeanId);
        if (bd.isLazy()) {  // 懒加载
            String classInfo = bd.getClassInfo();
            // Class.forName(classInfo).newInstance();的作用是动态加载并实例化一个类
            // Class.forName(xxx); 的作用是加载并初始化指定名称的类。
            Object beanObj = Class.forName(classInfo).newInstance();   // 创建对象
            beanMap.put(toDIBeanId, beanObj);
            return beanObj;
        }

        if ("prototype".equals(bd.getScope())){
            String classInfo = bd.getClassInfo();
            Object beanObj = Class.forName(classInfo).newInstance();   // 创建对象
            return beanObj;  // 不是懒加载，直接返回对象
        }
            return null;
    }


    /**
     * IOC
     */
    private void creatBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        for (Map.Entry<String, YcBeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanId = entry.getKey();
            YcBeanDefinition bd = entry.getValue();
            /**
             *  如果不是懒加载， Scope 不是 prototype 表示是单例
             *  这里创建对象，可以直接放入beanMap中
             */
            if (!bd.isLazy() && !"prototype".equals(bd.getScope())) {
                String classInfo = bd.getClassInfo();
//                Object beanObj = Class.forName(classInfo).getConstructor().newInstance();
                Object beanObj = Class.forName(classInfo).newInstance();
                beanMap.put(beanId, beanObj);
            }
        }

    }

    // 解析配置类中  @YcComponentScan注解   这代表配置类要扫描的包
    private void parseComponentScan(Class<?> cls) {
        if (cls.isAnnotationPresent(YcComponentScan.class)) {
            YcComponentScan ycComponentScan = cls.getAnnotation(YcComponentScan.class);
            // 取 YcComponentScan 的 value 属性值
            String[] basePackages = ycComponentScan.value();
            // 如果没有配置 value , 就取注解中写的的 basePackages=xxx
            if (basePackages == null || basePackages.length <= 0) {
                basePackages = ycComponentScan.basePackages();
            }
            // 如果 basePackages 还是为空，则说明 value 和 basePackages 都没有配置，则取当前类所在的包名
            if (basePackages == null || basePackages.length <= 0) {
                basePackages = new String[1];
                // cls.getPackage().getName(): 获取这个cls所在的包名   注意：全路径包名噶
                basePackages[0] = cls.getPackage().getName();
            }
            for (String basePackage : basePackages) {
                System.out.println("要扫描的路径：  " + basePackage);
            }
            /**
             * 这里完成的任务
             * 扫描指定路径下的所有.class文件
             * 查找所有 @YcComponent、@YcService、@YcRepository、@YcController 等注解的类
             * 信息添加到 beanDefinitionMap 中
             */
            recursiveLoadBeanDefinition(basePackages);
        }
    }


    /**
     * 递归扫描basePackage下的所有文件, 加载所有托管类的信息到BeanDefinition
     * basePackages: 要扫描的包名数组  例如： {org.springframework}
     *
     * @param basePackages
     */
    private void recursiveLoadBeanDefinition(String[] basePackages) {
        for (String onePackageName : basePackages) {
            // 因为是要系统中的文件中找 class 类，所以需要把包名中的 . 替换为 /
            String packagePath = onePackageName.replaceAll("\\.", "/");
            try {
                /**
                 * 1、获取的是一个Enumeration<URL>对象，它包含了一个或多个URL对象。这些URL对象表示了
                 *   在当前线程的上下文类加载器（Thread.currentThread().getContextClassLoader()）中查找到的资源文件的位置。
                 *
                 * 2、packagePath参数指定了一个包路径，例如"com/example/resources"。
                 *   getResources(packagePath) 方法会搜索该包路径下的所有资源文件，并返回
                 *   一个包含这些资源文件位置的Enumeration<URL>对象。
                 */
                // 类加载器里面的  线程上下文加载器
                Enumeration<URL> files = Thread.currentThread().getContextClassLoader().getResources(packagePath);
                // 遍历枚举类型
                while (files.hasMoreElements()) {
                    URL url = files.nextElement();
                    /**
                     * 注意：这里获取的url路径前面有个 /，所以需要去掉
                     * 例如："正在扫描的url路径：    /D:/java_workspace/allproject/DIYSpring/target/classes/org/springframework"
                     */
                    //  System.out.println("正在扫描的url路径：    " + url.getFile());
                    //  url.getFile() 获取的是前面多/的文件路径
                    //  onePackageName 包名,传进去做递归  例如： org.springframework
                    findPackageClasses(url.getFile(), onePackageName);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 扫描指定路径下的所有.class文件
     *
     * @param filePath       前面多'/'的文件路径
     * @param onePackageName 包名,传进去做递归  例如： org.springframework
     */
    private void findPackageClasses(String filePath, String onePackageName) {
        if (filePath.startsWith("/")) {
            filePath = filePath.substring(1);  // 去掉第一个 '/'
        }
        try {
            filePath = URLDecoder.decode(filePath, "utf-8"); // 防止路径中文，统一转utf-8
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        // file 对象代表了 filePath 文件或目录
        File file = new File(filePath);
        /**
         *  File.listFiles 方法是 Java 中用于列出指定目录下的所有文件和目录的方法。它有两种重载形式：
         *  1、无参数形式： public File[] listFiles()
         *     这种形式返回当前目录下的所有文件和目录，不包括子目录内的内容。
         *     返回的数组长度取决于当前目录下的文件和目录数量。
         *
         *  2、带参数形式： public File[] listFiles(FilenameFilter filter)
         *    这种形式接受一个实现了 FileFilter 接口的对象作为参数，用于过滤结果。
         *    只有满足 FileFilter 中 accept 方法定义的条件的文件和目录才会被包含在返回的数组中。
         */
        File[] classFiles = file.listFiles(new FileFilter() {
            /**
             * 判断给定文件是否符合过滤条件
             * @param pathname 给定的文件路径
             * @return 如果文件是.class文件或者是一个目录，则返回true；否则返回false
             *
             * FileFilter接口只有一个抽象方法boolean accept(File pathname)，
             * 这个方法决定了哪些路径名应该被包含在列表中。如果accept方法返回true，
             * 则该文件会被包含在返回的File数组中；否则，它将被过滤掉。
             */
            @Override
            public boolean accept(File pathname) {
                // 如果扫描到的是文件夹或者是.class文件，则返回true
                if (pathname.getName().endsWith(".class") || pathname.isDirectory()) {
                    return true;
                } else {
                    return false;
                }
            }
        });

        // 现在的 classFiles 是一个数组，经过过滤器之后  留下的只有 文件夹 和 .class 文件
        if (classFiles != null || classFiles.length > 0) {
            for (File cf : classFiles) {
                if (cf.isDirectory()) {
                    // cf.getAbsolutePath() 获取的是文件夹的绝对路径 例如：D:\java_workspace\allproject\DIYSpring\target\classes\org\springframework
                    findPackageClasses(cf.getAbsolutePath(), onePackageName + "." + cf.getName());
                } else {
                    try {
                        URLClassLoader uc = new URLClassLoader(new URL[]{});
                        // 加载类           com.yc.di         .     order.class         ---->   com.yc.di.order
                        // 拿到包里后缀是 .class 的文件对象
                        Class cls = uc.loadClass(onePackageName + "." + cf.getName().replace(".class", ""));
                        // 判断是否有Ioc注解， 如果有就托管， 先存到 BeanDefiniton
                        if (
                                cls.isAnnotationPresent(YcComponentScan.class) ||
                                        cls.isAnnotationPresent(YcComponent.class) ||
                                        cls.isAnnotationPresent(YcService.class) ||
                                        cls.isAnnotationPresent(YcController.class) ||
                                        cls.isAnnotationPresent(YcRepository.class)
                        ) {
                            // 创建bean定义信息对象
                            YcBeanDefinition bd = new YcBeanDefinition();
                            // 设置属性
                            if (cls.isAnnotationPresent(YcLazy.class)) {
                                YcLazy lazy = (YcLazy) cls.getAnnotation(YcLazy.class);
                                boolean b = lazy.value();
                                bd.setLazy(b);
                            }
                            if (cls.isAnnotationPresent(YcScope.class)) {  // 判断是否有Scope注解
                                YcScope scope = (YcScope) cls.getAnnotation(YcScope.class);
                                String s = scope.value();
                                bd.setScope(s);
                            }
                            // 拼接 ClassInfo 不要它的 .class
                            bd.setClassInfo(onePackageName + "." + cf.getName().replace(".class", ""));
                            // 拼接beanId  根据类名获取beanId
                            String beanId = getBeanId(cls);
                            // 存到beanDefinitionMap
                            beanDefinitionMap.put(beanId, bd);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        }

    }


    /**
     * 获取beanId
     * 查看是否有  YcComponent YcRepository  YcService  YcController 注解
     * 如果有，则获取注解的value值
     * 如果没有，则获取类名，首字母小写
     *
     * @param cls
     * @return
     */
    private static String getBeanId(Class cls) {
        String beanId = "";
        YcComponent ycComponent = (YcComponent) cls.getAnnotation(YcComponent.class);
        YcRepository ycRepository = (YcRepository) cls.getAnnotation(YcRepository.class);
        YcService ycService = (YcService) cls.getAnnotation(YcService.class);
        YcController ycController = (YcController) cls.getAnnotation(YcController.class);
        if (ycComponent != null) {
            beanId = ycComponent.value();
        } else if (ycRepository != null) {
            beanId = ycRepository.value();
        } else if (ycService != null) {
            beanId = ycService.value();
        } else if (ycController != null) {
            beanId = ycController.value();
        }
        if ("".equals(beanId)) {
            // 获取类名  Apple  --> apple
            String simpleName = cls.getSimpleName();
            beanId = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
        }
        return beanId;
    }


    // 解析配置类   解析有@YcConfiguration注解的类以及里面的@YcBean注解
    private void parseBeanAnnotation(Class<?> cls) {
        // 1、判断是否是配置类
        if (!cls.isAnnotationPresent(YcConfiguration.class)) {
            return;
        }
        // 2、解析配置类
        try {
            Object obj = cls.newInstance();   // 创建配置类对象
            String simpleName = cls.getSimpleName();   // 获取配置类的类名
            YcBeanDefinition bd = new YcBeanDefinition();  // 创建bean定义信息对象
            // 设置属性
            bd.setLazy(false);
            bd.setScope("singleton");
            bd.setClassInfo(cls.getName());

            // 拼接配置类的YcBeanDefinition的ID  作为key 先托管   首字母小写
            String beanId = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);

            beanDefinitionMap.put(beanId, bd);   // 保存到beanDefinitionMap中
            beanMap.put(beanId, obj);   // 保存到beanMap中  托管

            // 解析配置类中的方法
            // Method[] methods = cls.getMethods();  // 获取所有方法  包括父类中的方法  wait  equals  toString 、、、
            Method[] declaredMethods = cls.getDeclaredMethods();   // 获取所有方法,不包括父类中的方法
            for (Method m : declaredMethods) {
                // isAnnotationPresent: Java反射API中的一个方法，用于检测某个类、方法或字段是否有指定的注解
                if (m.isAnnotationPresent(YcBean.class)) {
                    // 获取注解对象
                    YcBean ycBean = m.getAnnotation(YcBean.class);
                    // 获取注解的value值, value默认是空字符串
                    String ycBeanId = ycBean.value();

                    // 如果value值是空，就取方法名作为托管对象的名字
                    if ("".equals(ycBeanId)) {
                        ycBeanId = m.getName();
                    }
                    // 拼接托管对象的名字  首字母小写
                    ycBeanId = ycBeanId.substring(0, 1).toLowerCase() + ycBeanId.substring(1);
                    // 创建bean定义信息对象
                    YcBeanDefinition bd1 = new YcBeanDefinition();
                    // 设置属性
                    bd1.setLazy(false);
                    bd1.setScope("singleton");
                    // 获取返回值的类全路径
                    bd1.setClassInfo(m.getReturnType().getName());
                    // 保存到beanDefinitionMap中   等待DI
                    this.beanDefinitionMap.put(ycBeanId, bd1);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String beanID) {
        YcBeanDefinition bd = this.beanDefinitionMap.get(beanID);
        if (bd == null){
            throw new RuntimeException("beanId:"+beanID+"不存在");
        }
        String scope = bd.getScope();
        if ("prototype".equals(scope)){
            Object obj = null;
            try {
                obj = Class.forName(bd.getClassInfo()).newInstance();
                this.beanMap.put(beanID,obj);
                doDI();
                this.beanMap.remove(beanID);
                return obj;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }
        }
        if (beanMap.containsKey(beanID)){
            return beanMap.get(beanID);
        }
        if (bd.isLazy()){
            Object obj = null;
            try {
                obj = Class.forName(bd.getClassInfo()).newInstance();
                beanMap.put(beanID,obj);
                doDI();
                return obj;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
}
